#!/usr/bin/python3
# -*- coding: UTF-8 -*-

import os
import cantools
import cantools.database
from pprint import pprint

from Utils import toCamelCase
from Utils import lowercaseFirstLetter
from Utils import binaryToHex

class CanMessage:
    def __init__(self, name:str, frameName:str):
        self.name = name
        self.frameName = frameName
        self.canSignals = []

    def initialize(self, message: cantools.database.Message):
        for signal in message.signals:
            signalObj = CanSignal()
            signalObj.initialize(signal)
            self.canSignals.append(signalObj)

        # for canSignal in self.canSignals :
        #     canSignal.printSignals()
    
    def generateSource(self, outDir:str):
        self.__generateHeadFile(outDir)
        self.__generateSourceFile(outDir)

    def __generateHeadFile(self, outDir:str):
        messageName = toCamelCase(self.name) + "Message"
        frameName = toCamelCase(self.name) + "Frame"
        headFileName = messageName + ".h"

        with open(outDir + "/" + headFileName, "w") as file:
            source = f"""#include \"../CanFrames/{frameName}.h\"\n\n\n"""
            source += f"""class {messageName}\n"""
            source += f"""{{\n"""
            source += f"""public:\n"""
            source += f"""\t{messageName}();\n"""
            source += f"""\t~{messageName}();\n\n"""
            source += f"""\tconst {frameName}& GetFrame();\n"""
            source += f"""\tvoid SetFrameByBuffer(const uint8_t* const frameBuffer);\n\n"""
            for signal in self.canSignals:
                signalType = "uint8_t"
                if signal.length <= 8:
                    signalType = "uint8_t"
                elif signal.length <= 16:
                    signalType = "uint16_t"
                elif signal.length <= 32:
                    signalType = "uint32_t"
                elif signal.length <= 64:
                    signalType = "uint64_t"
                source += f"""\tvoid Set{toCamelCase(signal.name)}(const {signalType}& value);\n"""
                source += f"""\t{signalType} Get{toCamelCase(signal.name)}();\n\n"""
            source += f"""private:\n"""
            source += f"""\t{frameName} m_{lowercaseFirstLetter(frameName)};\n"""
            source += f"""}};\n"""

            file.write(source)
            pass

    def __generateSourceFile(self, outDir:str):
        messageName = toCamelCase(self.name) + "Message"
        frameName = toCamelCase(self.name) + "Frame"
        sourceFileName = messageName + ".cpp"
        memberName = "m_" + lowercaseFirstLetter(frameName)

        with open(outDir + "/" + sourceFileName, "w") as file:
            source = f"""#include <string.h>\n\n\n"""
            source += f"""#include \"{messageName}.h\"\n\n\n"""
            source += f"""{messageName}::{messageName}(){{}}\n\n"""
            source += f"""{messageName}::~{messageName}(){{}}\n\n"""

            source += f"""const {frameName}& {messageName}::GetFrame()\n"""
            source += f"""{{\n"""
            source += f"""\treturn {memberName};\n"""
            source += f"""}}\n\n"""

            source += f"""void {messageName}::SetFrameByBuffer(const uint8_t* const frameBuffer)\n"""
            source += f"""{{\n"""
            source += f"""\tmemcpy(&{memberName}, frameBuffer, sizeof({memberName}));\n"""
            source += f"""}}\n\n"""

            for signal in self.canSignals:
                source += signal.generateSetGetSource(messageName, memberName)
            file.write(source)
            pass

class CanSignal:
    def __init__(self):
        self.name = ""
        self.length = 0
        self.bitFields = {}

    def initialize(self, signal:cantools.database.Signal):
        self.name = signal.name
        self.length = signal.length

        remainLength = self.length
        startBitInFrame = signal.start
        startBitInByte = startBitInFrame % 8
        valueIdx = 0
        while remainLength > 0:
            lengthInByte = startBitInByte + 1
            if remainLength <= lengthInByte:
                lengthInByte = remainLength
            bitField = CanSignalBitField()
            bitField.initialize(remainLength - 1, lengthInByte)
            self.bitFields[valueIdx] = bitField
            valueIdx += 1
            remainLength = remainLength - lengthInByte
            startBitInByte = 7

    def generateSetGetSource(self, messageName:str, memberName:str) -> str:

        signalType = "uint8_t"
        if self.length <= 8:
            signalType = "uint8_t"
        elif self.length <= 16:
            signalType = "uint16_t"
        elif self.length <= 32:
            signalType = "uint32_t"
        elif self.length <= 64:
            signalType = "uint64_t"

        retSource = f"""void {messageName}::Set{toCamelCase(self.name)}(const {signalType}& value)\n"""
        retSource += f"""{{\n"""
        retSource += f"""{self.__generateSetLogicSource(memberName)}"""
        retSource += f"""}}\n\n"""

        retSource += f"""{signalType} {messageName}::Get{toCamelCase(self.name)}()\n"""
        retSource += f"""{{\n"""
        retSource += f"""{self.__generateGetLogicSource(memberName)}"""
        retSource += f"""}}\n\n"""

        return retSource
    
    def __generateSetLogicSource(self, memberName:str) -> str:
        retSource = ""
        if len(self.bitFields) == 1:
            retSource = f"""\t{memberName}.{self.name} = value;\n"""
        else:
            shiftCount = 0
            for key in sorted(self.bitFields.keys(),reverse=True):
                bitfieldMark = self.__generateMark(self.bitFields[key].startBitInValue, self.bitFields[key].length)
                bitFieldName = self.name + "_" + str(key)
                retSource += f"""\t{memberName}.{bitFieldName} = (value & {bitfieldMark}) >> {shiftCount};\n"""
                shiftCount += self.bitFields.get(key).length
        return retSource
    
    def __generateGetLogicSource(self, memberName:str) -> str:
        signalType = "uint8_t"
        if self.length <= 8:
            signalType = "uint8_t"
        elif self.length <= 16:
            signalType = "uint16_t"
        elif self.length <= 32:
            signalType = "uint32_t"
        elif self.length <= 64:
            signalType = "uint64_t"
        shiftCount = 0
        retSource = ""
        if len(self.bitFields) == 1:
            retSource = f"""\treturn {memberName}.{self.name};\n"""
        else:
            retSource = f"""\t{signalType} retValue = 0;\n"""
            for key in sorted(self.bitFields.keys(),reverse=True):
                bitFieldName = self.name + "_" + str(key)
                retSource += f"""\tretValue += {signalType}({memberName}.{bitFieldName}) << {shiftCount};\n"""
                shiftCount += self.bitFields.get(key).length
            retSource += f"""\treturn retValue;\n"""
        return retSource
    
    def __generateMark(self, startBitInValue:int, length:int):
        binaryStr = ""
        for i in range(64):
            if i <= startBitInValue and i > (startBitInValue - length) :
                binaryStr = "1" + binaryStr
            else :
                binaryStr = "0" + binaryStr
        binaryStr = "0b" + binaryStr
        return binaryToHex(binaryStr)


    def printSignals(self):
        print("name " + self.name)
        for bitField in self.bitFields.values() :
            bitField.printInfo()
        pass
        

class CanSignalBitField:
    def __init__(self):
        self.startBitInValue = 0
        self.length = 0

    def initialize(self, startBitInvalue:int, length:int):
        self.startBitInValue = startBitInvalue
        self.length = length

    def printInfo(self):
        print("start " + str(self.startBitInValue))
        print("length " + str(self.length))
